<template>
  <div>
    <el-upload
      v-bind="$attrs"
      :action="action || upload.uploadUrl"
      :list-type="listType"
      :limit="limit"
      :accept="accept"
      :data="extraData"
      :file-list="fileList"
      :before-upload="beforeUpload"
      :on-success="onSuccess"
      :on-remove="onRemove"
      :before-remove="beforeRemove"
      :on-exceed="handleExceed"
      :on-preview="onPreview"
      :on-error="onError"
    >
      <i class="el-icon-plus" />
      <div v-if="currentTips" slot="tip" class="el-upload__tip">{{ currentTips }}</div>
    </el-upload>
    <el-image-viewer v-if="showViewer" :on-close="closeViewer" :url-list="viewer" />
  </div>
</template>

<script>
import { mapGetters, mapActions } from 'vuex'
import ElImageViewer from 'element-ui/packages/image/src/image-viewer'
import { parseTime, randomString } from '@/utils'

export default {
  name: 'Upload',
  components: { ElImageViewer },
  props: {
    action: {
      type: String,
      default: ''
    },
    listType: {
      type: String,
      default: 'picture-card'
    },
    limit: {
      type: Number,
      default: 1
    },
    accept: {
      type: String,
      default: 'image/jpg,image/jpeg,image/png'
    },
    size: {
      type: Number,
      default: 1
    },
    type: {
      type: String,
      default: 'image'
    },
    ratio: {
      type: String,
      default: ''
    },
    value: {
      type: [String, Array],
      default: ''
    },
    tips: {
      type: String,
      default: ''
    }
  },
  data () {
    return {
      key: '',
      date: parseTime(new Date(), '{y}-{m}-{d}'),
      fileList: [],
      showViewer: false
    }
  },
  computed: {
    extraData () {
      const { upload, key } = this
      const { token, config } = upload
      return { token: token, config, key }
    },
    imgRatio () {
      const { ratio } = this
      let res = 1
      if (ratio) {
        const sizeArr = ratio.split('*')
        const width = parseInt(sizeArr[0])
        const height = parseInt(sizeArr[1])
        res = width / height
      }
      return res
    },
    currentTips () {
      let tips = ''
      const { type, size, ratio, accept } = this
      if (ratio && type === 'image') {
        // tips += `建议上传尺寸为${ratio} `
      }
      if (size) {
        // tips += `大小不要超过${size}M `
      }
      if (accept) {
        // tips += `仅支持${accept}格式的${type === 'image' ? '图片' : '文件'}`
      }
      return this.tips + tips
    },
    viewer () {
      return this.fileList.map(item => item.url)
    },
    ...mapGetters({
      upload: 'd2admin/upload/upload'
    })
  },
  watch: {
    value: {
      handler: 'createFileList',
      immediate: true
    }
  },
  created () {
    this._getToken()
  },
  methods: {
    beforeUpload (file) {
      const { type, size, ratio, accept } = this
      const arr = []
      this.createKey(file)
      type === 'image' && ratio && arr.push(this.checkImgSize(file))
      size && arr.push(this.checkFileSize(file))
      accept && arr.push(this.checkAccept(file))
      return this.promiseArr(arr)
    },
    onSuccess ({ key }) {
      const { baseUrl } = this.upload
      const name = this.getFileName(key)
      const url = baseUrl + key
      this.fileList.push({ name, url })
      this.emitInput()
    },
    beforeRemove ({ status }) {
      if (status === 'success') {
        return this.$confirm(`确定移除该文件吗？`)
      }
    },
    onRemove (file = {}) {
      const { name, status } = file
      if (status === 'success') {
        const index = this.fileList.findIndex(item => item.name === name)
        index !== -1 && this.fileList.splice(index, 1)
        this.emitInput()
      }
    },
    emitInput () {
      const files = this.fileList.map(item => item.url)
      this.$emit('input', files.join(','))
    },
    handleExceed () {
      this.$message.warning(`当前限制选择 ${this.limit} 个文件`)
    },
    getFileName (url) {
      return url.substr(url.lastIndexOf('/') + 1)
    },
    createFileList (newVal) {
      const arr = typeof (newVal) === 'string' ? [newVal] : newVal
      const fileList = []
      arr.forEach(item => {
        const name = this.getFileName(newVal)
        fileList.push({ name, url: newVal })
      })
      this.fileList = newVal.length ? fileList : []
    },
    createKey ({ name, type }) {
      const { date, upload } = this
      const { key } = upload
      const fileSuffix = name.substring(name.lastIndexOf('.')) || ''
      const fileType = type.substring(0, type.lastIndexOf('/')) || ''
      this.key = `${key}/${fileType}/${date}/${randomString(8)}${fileSuffix}`
    },
    checkImgSize (file) {
      return new Promise((resolve, reject) => {
        const _URL = window.URL || window.webkitURL
        const img = new Image()
        img.src = _URL.createObjectURL(file)
        img.onload = () => {
          const valid = this.imgRatio === img.width / img.height
          const err = `图片${file.name}尺寸有误，请重新选择！`
          valid ? resolve(file) : reject(err)
        }
      })
    },
    checkFileSize (file) {
      return new Promise((resolve, reject) => {
        const err = `文件大小超过${this.size}M，请重新选择！`
        const valid = file.size / 1024 / 1024 < this.size
        valid ? resolve(file) : reject(err)
      })
    },
    checkAccept (file) {
      return new Promise((resolve, reject) => {
        const err = `文件类型不正确，请重新选择！`
        const valid = this.accept.indexOf(file.type) === -1
        valid ? reject(err) : resolve(file)
      })
    },
    async promiseArr (arr) {
      return Promise.all(arr).then(res => {
        return Promise.resolve(res)
      }).catch(err => {
        this.$message.error(err)
        return Promise.reject(err)
      })
    },
    onPreview () {
      const { type } = this
      type === 'image' && (this.showViewer = true)
    },
    closeViewer () {
      this.showViewer = false
    },
    onError () {
      this.$message.warning('上传失败，请刷新页面再试！')
    },
    _getToken () {
      const { token } = this.upload
      if (!token) {
        this.getToken()
      }
    },
    ...mapActions({
      getToken: 'd2admin/upload/getToken'
    })
  }
}
</script>
